package org.xx.armory.spring5.junit;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.XMLConstants;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;

import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 从XML文件中读取数据库记录的类
 *
 * @author Haart
 */
public abstract class DbUnitTableReader {
    private static final String ROOT_ELEMENT_NAME = "rows";
    private static final String ROW_ELEMENT_NAME = "row";
    private static final String DB_DATA_SCHEMA = "/db-data.xsd";

    private DbUnitTableReader() {
        throw new AssertionError();
    }

    /**
     * 创建一个使用SAX方式解析XML的解析器工厂。这个工厂使用 {@link #DB_DATA_SCHEMA} 作为schema。
     *
     * @return 用于创建 {@link SAXParser} 的工厂。
     */
    private static SAXParserFactory createParserFactory() {
        try {
            final var schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            try (final var xsdStream = DbUnitTableReader.class.getResourceAsStream(DB_DATA_SCHEMA)) {
                final var schema = schemaFactory.newSchema(new StreamSource(xsdStream));
                final var parserFactory = SAXParserFactory.newInstance();

                parserFactory.setSchema(schema);

                return parserFactory;
            }
        } catch (IOException ex1) {
            throw new UncheckedIOException(ex1);
        } catch (SAXException ex2) {
            throw new IllegalStateException("Cannot load schema: " + DB_DATA_SCHEMA, ex2);
        }
    }

    /**
     * 从XML文件中读取指定数据库表的记录
     *
     * @param stream
     *         包含XML文件的流
     * @return 成功读取的记录列表, 每条记录是一个{@link java.util.Map}类型的变量。
     * @throws IllegalArgumentException
     *         如果参数 {@code tableName}是 {@code null}或者参数 {@code stream}是 {@code null}。
     * @throws UncheckedIOException
     *         如果从流中读取数据出错。
     * @throws IllegalStateException
     *         如果创建 {@link javax.xml.parsers.SAXParser}对象时出错，或者解析XML文件出错。
     */
    public static Collection<Map<String, String>> readAllRows(
            InputStream stream
    ) {
        rejectIfNull(stream, "stream");

        final var result = new ArrayList<Map<String, String>>();

        SAXParser parser;
        try {
            parser = ParserFactoryHolder.INSTANCE.newSAXParser();
        } catch (ParserConfigurationException | SAXException ex) {
            throw new IllegalStateException("Cannot create SAXParser", ex);
        }

        try {
            parser.parse(stream, new DefaultHandler() {
                @Override
                public void startElement(
                        String uri,
                        String localName,
                        String qName,
                        Attributes attributes
                )
                        throws SAXException {
                    super.startElement(uri, localName, qName, attributes);

                    switch (qName) {
                        case ROOT_ELEMENT_NAME:
                            result.clear();
                            break;
                        case ROW_ELEMENT_NAME:
                            result.add(readRow(attributes));
                            break;
                        default:
                            throw new IllegalStateException("Unknown element: " + qName);
                    }
                }
            });
        } catch (IOException ex1) {
            throw new UncheckedIOException(ex1);
        } catch (SAXException ex2) {
            throw new IllegalStateException("Cannot parse xml", ex2);
        }


        return result;
    }

    /**
     * 解析 {@literal row} 标签。每个标签表示数据库的一条记录。
     *
     * @param attributes
     *         需要解析的 {@literal row} 标签的所有属性。
     * @return 读到的一条记录。
     * @throws IllegalArgumentException
     *         如果参数{@code attributes}是{@code null}。
     */
    public static Map<String, String> readRow(
            Attributes attributes
    ) {
        rejectIfNull(attributes, "attributes");

        final var result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);

        for (var i = 0; i < attributes.getLength(); ++i) {
            result.put(attributes.getLocalName(i), attributes.getValue(i));
        }

        return result;
    }

    private static final class ParserFactoryHolder {
        static final SAXParserFactory INSTANCE = createParserFactory();
    }

}
